Actualización de MongoDB campo utilizando valor de otro campo
-
09-10-2019 - |
Pregunta
En MongoDB, es posible actualizar el valor de un campo utilizando el valor de otro campo? El SQL equivalente sería algo como:
UPDATE Person SET Name = FirstName + ' ' + LastName
Y el pseudo-código MongoDB sería:
db.person.update( {}, { $set : { name : firstName + ' ' + lastName } );
Solución
La mejor manera de hacer esto es en la versión 4.2 y versiones posteriores, que permite el uso de tubería de agregación en el documento de actualización y la updateOne
, updateMany
o update
método de recolección. Tenga en cuenta que este último ya no se utiliza en la mayoría si no todos los conductores idiomas.
MongoDB 4,2 +
Version 4.2 también introdujo el href="https://docs.mongodb.com/master/reference/operator/aggregation/set/" operador rel="nofollow noreferrer"> $set
etapa de canalización $addFields
. Voy a utilizar $set
aquí, ya que mapas con lo que estamos tratando de lograr.
db.collection.<update method>(
{},
[
{"$set": {"name": { "$concat": ["$firstName", " ", "$lastName"]}}}
]
)
MongoDB 3,4 +
En 3.4+ puede utilizar $addFields
y la $out
operadores de tuberías agregación.
db.collection.aggregate(
[
{ "$addFields": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}},
{ "$out": "collection" }
]
)
Tenga en cuenta que este no actualiza su colección sino que reemplazar la colección existente o crear una nueva. También para las operaciones de actualización que requieren "conversión de tipo" tendrá el procesamiento del lado del cliente, y, dependiendo de la operación, es posible que tenga que utilizar el método find()
en lugar del método .aggreate()
.
MongoDB 3.2 y 3.0
La forma de hacer esto es mediante la $project
ing nuestros documentos y utilizar el operador $concat
agregación cadena a devolver la cadena concatenada.
A partir de ahí que, a continuación, iterar el cursor y el uso de la $set
actualización para agregar el nuevo campo a sus documentos utilizando operaciones masivas para una máxima eficiencia.
Agregación de consulta:
var cursor = db.collection.aggregate([
{ "$project": {
"name": { "$concat": [ "$firstName", " ", "$lastName" ] }
}}
])
MongoDB 3.2 o posterior
de esto, es necesario utilizar el bulkWrite
método.
var requests = [];
cursor.forEach(document => {
requests.push( {
'updateOne': {
'filter': { '_id': document._id },
'update': { '$set': { 'name': document.name } }
}
});
if (requests.length === 500) {
//Execute per 500 operations and re-init
db.collection.bulkWrite(requests);
requests = [];
}
});
if(requests.length > 0) {
db.collection.bulkWrite(requests);
}
MongoDB 2,6 y 3,0
A partir de esta versión se necesita usar el obsoleto Bulk
API y su métodos asociados .
var bulk = db.collection.initializeUnorderedBulkOp();
var count = 0;
cursor.snapshot().forEach(function(document) {
bulk.find({ '_id': document._id }).updateOne( {
'$set': { 'name': document.name }
});
count++;
if(count%500 === 0) {
// Excecute per 500 operations and re-init
bulk.execute();
bulk = db.collection.initializeUnorderedBulkOp();
}
})
// clean up queues
if(count > 0) {
bulk.execute();
}
MongoDB 2.4
cursor["result"].forEach(function(document) {
db.collection.update(
{ "_id": document._id },
{ "$set": { "name": document.name } }
);
})
Otros consejos
Se debe recorrer. Para su caso específico:
db.person.find().snapshot().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);
Al parecer hay una manera de hacer esto de manera eficiente desde MongoDB 3.4, consulte la respuesta .
Respuesta obsoleto a continuación
No se puede hacer referencia al documento en sí mismo en una actualización (todavía). Tendrá que repetir por los documentos y actualizar cada documento utilizando una función. Ver esta respuesta para un ejemplo o éste para eval()
del lado del servidor.
Para una base de datos con alta actividad, es posible que tenga problemas en sus cambios afectan a los registros que cambian de forma activa y por esta razón por la que recomendamos el uso de instantánea ()
db.person.find().snapshot().forEach( function (hombre) {
hombre.name = hombre.firstName + ' ' + hombre.lastName;
db.person.save(hombre);
});
http://docs.mongodb.org/manual/reference/method /cursor.snapshot/
Me trató la solución anterior, pero me pareció que no sea adecuado para grandes cantidades de datos. Entonces descubrí la característica de corriente:
MongoClient.connect("...", function(err, db){
var c = db.collection('yourCollection');
var s = c.find({/* your query */}).stream();
s.on('data', function(doc){
c.update({_id: doc._id}, {$set: {name : doc.firstName + ' ' + doc.lastName}}, function(err, result) { /* result == true? */} }
});
s.on('end', function(){
// stream can end before all your updates do if you have a lot
})
})
Con respecto a este respuesta , la función instantánea está en desuso en la versión 3.6, de acuerdo con esta actualización . Así, en la versión 3.6 y superiores, es posible realizar la operación de esta manera:
db.person.find().forEach(
function (elem) {
db.person.update(
{
_id: elem._id
},
{
$set: {
name: elem.firstname + ' ' + elem.lastname
}
}
);
}
);
Esto es lo que se nos ocurrió para la copia de un campo a otro para ~ 150_000 registros. Tomó cerca de 6 minutos, pero sigue siendo significativamente menos recursos de lo que hubiera sido para crear instancias y iterar sobre el mismo número de objetos rubí.
js_query = %({
$or : [
{
'settings.mobile_notifications' : { $exists : false },
'settings.mobile_admin_notifications' : { $exists : false }
}
]
})
js_for_each = %(function(user) {
if (!user.settings.hasOwnProperty('mobile_notifications')) {
user.settings.mobile_notifications = user.settings.email_notifications;
}
if (!user.settings.hasOwnProperty('mobile_admin_notifications')) {
user.settings.mobile_admin_notifications = user.settings.email_admin_notifications;
}
db.users.save(user);
})
js = "db.users.find(#{js_query}).forEach(#{js_for_each});"
Mongoid::Sessions.default.command('$eval' => js)
A partir Mongo 4.2
, db.collection.update()
puede aceptar una tubería de agregación, permitiendo finalmente la actualización / creación de un campo basado en otro campo:
// { firstName: "Hello", lastName: "World" }
db.collection.update(
{},
[{ $set: { name: { $concat: [ "$firstName", " ", "$lastName" ] } } }],
{ multi: true }
)
// { "firstName" : "Hello", "lastName" : "World", "name" : "Hello World" }
-
La primera parte es la consulta
{}
partido, el filtrado de los documentos a la actualización (en nuestro caso todos los documentos). -
La segunda parte
[{ $set: { name: { ... } }]
es la tubería actualización agregación (tenga en cuenta los corchetes significan el uso de una tubería de agregación).$set
es un nuevo operador de agregación y un alias de$addFields
. -
No se olvide
{ multi: true }
, de lo contrario sólo el primer documento correspondiente se actualizará.